// original IT0051 by thejam79
// add YV12 mode by minamina 2003/05/01
//
// Borrowed from the author of IT.dll, whose name I
// could not determine. Modified for YV12 by Donald Graft.
// RGB32 Added by Klaus Post
// Converted to generic planar, and now using exact coordinates - NOT character coordinates by Klaus Post
// Refactor, DrawString...() is the primative, Oct 2010 Ian Brabham
// TO DO: Clean up - and move functions to a .c file.

// pinterf:
// high bit depth, planar RGB
// utf8 option, internally unicode
// info_h font definition reorganized, Latin-1 Supplement 00A0-00FF
// Configurable color
// Configurable halocolor (text outline)
// Configurable background fading
// Alignment
// multiline
// multiple size, multiple fonts, "Terminus", "info_h"

#include "info.h"
#include <cstring>
#include <sstream>
#include <fstream>
#include <unordered_map>
#include <array>
#include <iomanip>
#include <avs/filesystem.h>

#include "fonts/fixedfonts.h"
#include "strings.h"

// generate outline on-the-fly
// consider: thicker outline for font sizes over 24?
void BitmapFont::generateOutline(uint16_t* outlined, int fontindex) const
{
  const uint16_t* currentfont = &font_bitmaps[height * fontindex];
  for (int i = 0; i < height; i++)
    outlined[i] = 0;

  auto make_dizzyLR = [](uint16_t fontline) {
    return (uint16_t)((fontline << 1) | (fontline >> 1));
  };
  auto make_dizzyLCR = [](uint16_t fontline) {
    return (uint16_t)(fontline | (fontline << 1) | (fontline >> 1));
  };

  // font bitmap is left (msb) aligned, if width is less than 16
  // fixme: why? because having 0x8000 as a fixed position rendering mask?
  const uint16_t mask = ((1 << width) - 1) << (16 - width);
  // 5432109876543210
  // 0000001111111111

  uint16_t prev_line = 0;
  uint16_t current_line = 0;
  uint16_t next_line;
  for (int i = 0; i < height - 1; i++)
  {
    current_line = currentfont[i];
    next_line = currentfont[i + 1];
    uint16_t line = make_dizzyLCR(prev_line) | make_dizzyLR(current_line) | make_dizzyLCR(next_line);

    uint16_t value = (line & ~current_line) & mask;
    outlined[i] = value;

    prev_line = current_line;
    current_line = next_line;
  }
  // last one, no next line
  uint16_t line = make_dizzyLCR(prev_line) | make_dizzyLR(current_line) | make_dizzyLCR(0);
  uint16_t value = (line & ~current_line) & mask;
  outlined[height - 1] = value;
}

// helper function for remapping a wchar_t string to font index entry list
std::vector<int> BitmapFont::remap(const std::wstring& ws)
{
  // new vector with characters remapped to table indexes
  std::vector<int> s_remapped;
  s_remapped.resize(ws.size());
  for (size_t i = 0; i < ws.size(); i++) {
    auto it = charReMap.find(ws[i]);
    if (it != charReMap.end())
      s_remapped[i] = it->second;
    else
      s_remapped[i] = 0; // empty neutral character (space)
  }
  return s_remapped;
}

// Internal function! For creating source code from a previously LoadBDF'd font file
// see fixedfonts.cpp
void BitmapFont::SaveAsC(const uint16_t* _codepoints)
{
  if (font_filename == "") return; // no GUS no sound :)

  std::string fontname;
  if (font_filename.substr(0, 4) == "ter-")
    font_name = "Terminus";
  else
    font_name = font_filename;

  std::ostringstream ss;
  ss << "namespace fixed_font_# {" << std::endl;
  ss << "// -- start of autogenerated text ---" << std::endl;
  ss << "// definition section for font: " << font_filename << std::endl;
  ss << "constexpr int CHARCOUNT = " << std::to_string(number_of_chars) << ";" << std::endl;
  ss << "constexpr int WIDTH = " << std::to_string(width) << ";" << std::endl;
  ss << "constexpr int HEIGHT = " << std::to_string(height) << ";" << std::endl;
  ss << "constexpr FixedFont_info_t fixedfont_info = {" << std::endl;
  ss << "  \"" << font_filename << "\", // font name" << std::endl;
  ss << "  \"" << font_filename << "\", // font name internal" << std::endl;
  ss << "  CHARCOUNT, // num of chars" << std::endl;
  ss << "  WIDTH," << std::endl;
  ss << "  HEIGHT," << std::endl;
  ss << "  " << (bold ? "true" : "false") << " // bold" << std::endl;
  ss << "};" << std::endl;
  ss << "// font bitmap definitions" << std::endl;
  ss << "constexpr std::array<uint16_t, CHARCOUNT * HEIGHT> fixedfont_bitmap = {" << std::endl;
  for (int charcount = 0; charcount < number_of_chars; charcount++)
  {
    constexpr int LINES_BY_N = 16; // maximum number of constants per line
    for (int y = 0; y < height; y++) {
      int charline = font_bitmaps[charcount * height + y];
      ss << "0x" << std::setfill('0') << std::setw(4) << std::hex << charline;
      const bool last = charcount == number_of_chars - 1 && y == height - 1;
      if (!last) ss << ",";
      if (y == height - 1)
        ss << " // u" << std::setw(4) << std::hex << _codepoints[charcount];
      if (y % LINES_BY_N == LINES_BY_N - 1) ss << std::endl; // last line of character- forced new line
    }
    if (height % LINES_BY_N != 0)
      ss << std::endl;
  };
  // example output:
  // 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,
  // 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, // u0020
  // 0x0000,0x0000,0x0000,0x0000,0x0300,0x0300,0x0300,0x0300,0x0300,0x0300,0x0300,0x0300,0x0300,0x0300,0x0300,0x0300,
  // 0x0000,0x0000,0x0300,0x0300,0x0300,0x0300,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, // u0021

  ss << "};" << std::endl;
  ss << "// codepoints array" << std::endl;
  ss << "constexpr std::array<uint16_t, CHARCOUNT> fixedfont_codepoints = {" << std::endl;
  for (int charcount = 0; charcount < number_of_chars; charcount++)
  {
    constexpr int LINES_BY_N = 16;
    int val = _codepoints[charcount];
    ss << "0x" << std::setfill('0') << std::setw(4) << std::hex << val;
    const bool last = charcount == number_of_chars - 1;
    if (!last) ss << ",";
    if (charcount % LINES_BY_N == LINES_BY_N - 1) ss << std::endl;
  }
  ss << "};" << std::endl;

  ss << "} // namespace" << std::endl;
  ss << "// -- end of autogenerated text ---" << std::endl;
  //
  std::ofstream outFile(font_filename + ".cpp_sample");
  outFile << ss.str();
  outFile.close();

  /* usage:
  // Predefined fonts main table
  constexpr int PREDEFINED_FONT_COUNT = 2;
  static const uint16_t* font_bitmaps[PREDEFINED_FONT_COUNT] =
  {
    &fixed_font_1::fixedfont_bitmap[0],
    &fixed_font_2::fixedfont_bitmap[0]
    // ...
  };
  static const uint16_t* font_codepoints[PREDEFINED_FONT_COUNT] =
  {
    &fixed_font_1::fixedfont_codepoints[0],
    &fixed_font_2::fixedfont_codepoints[0]
    // ...
  };
  static const FixedFont_info_t* font_infos[PREDEFINED_FONT_COUNT] =
  {
    &fixed_font_1::fixedfont_info,
    &fixed_font_2::fixedfont_info
    // ...
  };
  */
}

typedef struct CharInfo { // STARTCHAR charname
  std::string friendly_name;
  uint16_t encoding;
} CharDef;

typedef struct FontProperties {
  std::string Copyright;
  std::string Notice;
  std::string Family_name;
  std::string Weight_name;
  int pixel_size;
  int font_ascent; // 12
  int font_descent; // 4
  uint16_t default_char; // 65533
} FontProperties;

typedef struct FontInfo {
  std::string font; // n/a
  int sizeA, sizeB, sizeC; // n/a
  int font_bounding_box_x;
  int font_bounding_box_y;
  int font_bounding_box_what;
  int font_bounding_box_what2; // -4 e.g. baseline?
  int chars; // number of characters
} FontInfo;

class BdfFont {
public:
  std::string font_filename;
  FontInfo font_info;
  FontProperties font_properties;
  std::vector<uint16_t> codepoints_array;
  std::vector<std::string> charnames_array;
  std::vector<uint16_t> font_bitmaps; // uint16_t: max. width is 16
};

#include <locale>
#include <fstream>

std::string UnQuote(std::string s) {
  if (s.size() >= 2 && s.substr(0, 1) == "\"" && (s.substr(s.size() - 1, 1) == "\""))
    return s.substr(1,s.size()-2); // zero based
  return s;
}

static BdfFont LoadBMF(std::string name, bool bold) {

  // https://en.wikipedia.org/wiki/Glyph_Bitmap_Distribution_Format
  // also for make cpp source code, see BdfFont methods

  enum loadstate { ls_Init, ls_StartFont, ls_StartProperties, ls_Char };

  BdfFont fnt;
  CharInfo current_char;

  std::string temp;
  //std::ifstream ss("c:\\Download\\terminus-font-4.48\\terminus-font-4.48\\ter-u16n.bdf");
  std::ifstream ss;
  // explicite font file name
  auto fname = fs::path(name);

  if (!fs::exists(name))
    return fnt;

  fnt.font_filename = fname.filename().generic_string();
  ss.open(name);

  loadstate state = ls_Init;

  size_t line_counter = 0;
  int char_counter = 0;

  // make list governed by LF separator
  while (std::getline(ss, temp, '\n')) {
    std::string token;
    std::istringstream ssline(temp);

    std::getline(ssline, token, ' ');

    if (token.size() == 0) continue;

    switch (state) {
    case ls_Init:
      if (token == "STARTFONT")
        state = ls_StartFont;
      else {
        // unexpected token
      }
      break;
    case ls_StartFont:
      if (token == "ENDFONT") {
        state = ls_Init;
      }
      else if (token == "FONT") {
        // FONT - xos4 - Terminus - Bold - R - Normal--16 - 160 - 72 - 72 - C - 80 - ISO10646 - 1
        std::getline(ssline, token, ' ');
        fnt.font_info.font = UnQuote(token);
      }
      else if (token == "SIZE") {
        // SIZE 16 72 72
        std::getline(ssline, token, ' ');
        fnt.font_info.sizeA = std::stoi(token);
        std::getline(ssline, token, ' ');
        fnt.font_info.sizeB = std::stoi(token);
        std::getline(ssline, token, ' ');
        fnt.font_info.sizeC = std::stoi(token);
      }
      else if (token == "FONTBOUNDINGBOX") {
        // FONTBOUNDINGBOX 8 16 0 - 4
        std::getline(ssline, token, ' ');
        fnt.font_info.font_bounding_box_x = std::stoi(token);
        std::getline(ssline, token, ' ');
        fnt.font_info.font_bounding_box_y = std::stoi(token);
        std::getline(ssline, token, ' ');
        fnt.font_info.font_bounding_box_what = std::stoi(token);
        std::getline(ssline, token, ' ');
        fnt.font_info.font_bounding_box_what2 = std::stoi(token);
      }
      else if (token == "STARTPROPERTIES") {
        // STARTPROPERTIES 20
        // do not check line count now
        state = ls_StartProperties;
      }
      else if (token == "CHARS") {
        std::getline(ssline, token);
        fnt.font_info.chars = std::stoi(token);
        // allocate area for "character_count" * "height"
        fnt.font_bitmaps.resize(fnt.font_info.chars* fnt.font_info.font_bounding_box_y);
        fnt.charnames_array.resize(fnt.font_info.chars);
        fnt.codepoints_array.resize(fnt.font_info.chars);
      }
      else if (token == "STARTCHAR") {
        std::getline(ssline, token);
        current_char.friendly_name = token;
        current_char.encoding = 0;
        state = ls_Char;
      }
      else {
        // unexpected token
      }
      break;
    case ls_StartProperties:
      if (token == "ENDPROPERTIES") {
        state = ls_StartFont;
      }
      /*
      FAMILY_NAME "Terminus"
        FOUNDRY "xos4"
        SETWIDTH_NAME "Normal"
        ADD_STYLE_NAME ""
        COPYRIGHT "Copyright (C) 2019 Dimitar Toshkov Zhekov"
        NOTICE "Licensed under the SIL Open Font License, Version 1.1"
        WEIGHT_NAME "Bold"
        SLANT "R"
        PIXEL_SIZE 16
        POINT_SIZE 160
        RESOLUTION_X 72
        RESOLUTION_Y 72
        SPACING "C"
        AVERAGE_WIDTH 80
        CHARSET_REGISTRY "ISO10646"
        CHARSET_ENCODING "1"
        MIN_SPACE 8
        */
      else if (token == "FAMILY_NAME") {
        //FAMILY_NAME "Terminus"
        std::getline(ssline, token, ' ');
        fnt.font_properties.Family_name = UnQuote(token);
      }
      else if (token == "COPYRIGHT") {
        std::getline(ssline, token, ' ');
        fnt.font_properties.Copyright = UnQuote(token);
      }
      else if (token == "NOTICE") {
        std::getline(ssline, token, ' ');
        fnt.font_properties.Notice = UnQuote(token);
      }
      else if (token == "WEIGHT_NAME") {
        // "Medium", "Bold"
        std::getline(ssline, token, ' ');
        fnt.font_properties.Weight_name = UnQuote(token);
      }
      else if (token == "PIXEL_SIZE") {
        //FAMILY_NAME "Terminus"
        std::getline(ssline, token, ' ');
        fnt.font_properties.pixel_size = std::stoi(token);
      }
      else if (token == "FONT_ASCENT") {
        // FONT_ASCENT 12
        std::getline(ssline, token, ' ');
        fnt.font_properties.font_ascent = std::stoi(token);
      }
      else if (token == "FONT_DESCENT") {
        //FONT_DESCENT 4
        std::getline(ssline, token, ' ');
        fnt.font_properties.font_descent = std::stoi(token);
      }
      else if (token == "DEFAULT_CHAR") {
        // DEFAULT_CHAR 65533
        std::getline(ssline, token, ' ');
        fnt.font_properties.default_char = std::stoi(token);
      }
      break;
    case ls_Char:
      if (token == "ENDCHAR") {
        // add to vector
        state = ls_StartFont;
      }
      else if (token == "ENCODING") {
        // ENCODING 32
        std::getline(ssline, token, ' ');
        current_char.encoding = std::stoi(token);
      }
      else if (token == "BBX") {
        // BBX 8 16 0 - 4
        std::getline(ssline, token, ' ');
        int w = std::stoi(token);
        std::getline(ssline, token, ' ');
        int h = std::stoi(token);
        if (w != fnt.font_info.font_bounding_box_x || h != fnt.font_info.font_bounding_box_y)
        {
          // bitmap dimensions do not match, we are handling fixed fonts where
        }
      }
      else if (token == "BITMAP") {
        /* 6x12:
          BITMAP
          00
          00
          70
          88
          08
          30
          08
          08
          88
          70
          00
          00
          ENDCHAR

          10x18:
          BITMAP
          0000
          0000
          0000
          3F00
          6180
          6180
          0180
          0180
          1F00
          0180
          0180
          0180
          6180
          6180
          3F00
          0000
          0000
          0000
          ENDCHAR
        */
        fnt.codepoints_array[char_counter] = current_char.encoding;
        fnt.charnames_array[char_counter] = current_char.friendly_name;
        char_counter++;
        for(int count = 0; count < fnt.font_info.font_bounding_box_y; count++)
        {
          uint16_t charline;
          std::getline(ss, temp);
          std::stringstream sss(temp);
          sss >> std::hex >> charline;
          // charlines are left aligned. Over 8 bits they come on 2 bytes
          if(fnt.font_info.font_bounding_box_x<=8)
            charline <<= 8; // less than 8 bits is on a single byte. Make it uint16_t left aligned

          fnt.font_bitmaps[line_counter++] = charline;
        }
      }
      break;
    }
  }

  return fnt;
}
  /*

  STARTFONT 2.1
    FONT -xos4-Terminus-Bold-R-Normal--16-160-72-72-C-80-ISO10646-1
    SIZE 16 72 72
    FONTBOUNDINGBOX 8 16 0 -4

    STARTPROPERTIES 20
      FAMILY_NAME "Terminus"
      FOUNDRY "xos4"
      SETWIDTH_NAME "Normal"
      ADD_STYLE_NAME ""
      COPYRIGHT "Copyright (C) 2019 Dimitar Toshkov Zhekov"
      NOTICE "Licensed under the SIL Open Font License, Version 1.1"
      WEIGHT_NAME "Bold"
      SLANT "R"
      PIXEL_SIZE 16
      POINT_SIZE 160
      RESOLUTION_X 72
      RESOLUTION_Y 72
      SPACING "C"
      AVERAGE_WIDTH 80
      CHARSET_REGISTRY "ISO10646"
      CHARSET_ENCODING "1"
      MIN_SPACE 8
      FONT_ASCENT 12
      FONT_DESCENT 4
      DEFAULT_CHAR 65533
    ENDPROPERTIES

    CHARS 1354

    STARTCHAR space
      ENCODING 32
      SWIDTH 500 0
      DWIDTH 8 0
      BBX 8 16 0 -4
      BITMAP
      00
      00
      00
      00
      00
      00
      00
      00
      00
      00
      00
      00
      00
      00
      00
      00
    ENDCHAR

  ENDFONT
  */

static constexpr int ATA_LEFT = 1;
static constexpr int ATA_RIGHT = 2;
static constexpr int ATA_CENTER = 4;

static constexpr int ATA_TOP = 8;
static constexpr int ATA_BOTTOM = 16;
static constexpr int ATA_BASELINE = 32;

static int alignToBitmask(int align_1_to_9)
{
  // alignment 1-9: digit positions on numeric keypad
  int al = 0;
  switch (align_1_to_9) // This spec where [X, Y] is relative to the text (inverted logic)
  {
  case 1: al = ATA_BOTTOM | ATA_LEFT; break;     // .----
  case 2: al = ATA_BOTTOM | ATA_CENTER; break;   // --.--
  case 3: al = ATA_BOTTOM | ATA_RIGHT; break;    // ----.
  case 4: al = ATA_BASELINE | ATA_LEFT; break;   // .____
  case 5: al = ATA_BASELINE | ATA_CENTER; break; // __.__
  case 6: al = ATA_BASELINE | ATA_RIGHT; break;  // ____.
  case 7: al = ATA_TOP | ATA_LEFT; break;        // `----
  case 8: al = ATA_TOP | ATA_CENTER; break;      // --`--
  case 9: al = ATA_TOP | ATA_RIGHT; break;       // ----`
  default: al = ATA_BASELINE | ATA_LEFT; break;  // .____
  }
  return al;
}

static int getColorForPlane(int plane, int color)
{
  switch (plane) {
  case PLANAR_A:
    return (color >> 24) & 0xff; break;
  case PLANAR_R:
  case PLANAR_Y:
    return (color >> 16) & 0xff; break;
  case PLANAR_G:
  case PLANAR_U:
    return (color >> 8) & 0xff; break;
  case PLANAR_B:
  case PLANAR_V:
    return color & 0xff; break;
  }
  return color & 0xFF;
}

template<bool fadeBackground>
void AVS_FORCEINLINE LightOnePixelYUY2(const bool lightIt, BYTE* dp, int val_color, int val_color_U, int val_color_V)
{
  if (lightIt) { // character definition bits aligned to msb
    if (size_t(dp) & 2) { // Assume dstp is dword aligned
      dp[0] = val_color;
      dp[-1] = val_color_U;
      dp[1] = val_color_V;
    }
    else {
      dp[0] = val_color;
      dp[1] = val_color_U;
      dp[3] = val_color_V;
    }
  }
  else {
    if constexpr (fadeBackground) {
      if (size_t(dp) & 2) {
        dp[0] = (unsigned char)((dp[0] * 7) >> 3) + 2;
        dp[-1] = (unsigned char)((dp[-1] * 7) >> 3) + 16;
        dp[1] = (unsigned char)((dp[1] * 7) >> 3) + 16;
      }
      else {
        dp[0] = (unsigned char)((dp[0] * 7) >> 3) + 2;
        dp[1] = (unsigned char)((dp[1] * 7) >> 3) + 16;
        dp[3] = (unsigned char)((dp[3] * 7) >> 3) + 16;
      }
    }
  }
}

template<typename pixel_t, bool fadeBackground>
void AVS_FORCEINLINE LightOnePixelRGB(const bool lightIt, BYTE* _dp, int val_color_R, int val_color_G, int val_color_B)
{
  pixel_t* dp = reinterpret_cast<pixel_t*>(_dp);
  if (lightIt) { // character definition bits aligned to msb
    dp[0] = val_color_B;
    dp[1] = val_color_G;
    dp[2] = val_color_R;
  }
  else {
    if constexpr (fadeBackground) {
      dp[0] = (pixel_t)((dp[0] * 7) >> 3);
      dp[1] = (pixel_t)((dp[1] * 7) >> 3);
      dp[2] = (pixel_t)((dp[2] * 7) >> 3);
    }
  }
}

template<typename pixel_t, int bits_per_pixel, bool fadeBackground, bool isRGB>
void AVS_FORCEINLINE LightOnePixel(const bool lightIt, pixel_t* dstp, int j, pixel_t& val_color)
{
  if (lightIt) { // character definition bits aligned to msb
    dstp[j] = val_color;
  }
  else {
    // 16 = y_min
    // speed optimization: one subtraction less, 5-8% faster
    // (((Y - 16) * 7) >> 3) + 16 = ((Y * 7) >> 3) + 2
    // in general: ((Y * 7) >> 3) + n, where n = range_min - ((range_min * 7) >> 3)
    if constexpr (fadeBackground) {
      // background darkening
      if constexpr (isRGB) {
        if constexpr (sizeof(pixel_t) != 4)
          dstp[j] = (pixel_t)((dstp[j] * 7) >> 3);
        else {
          constexpr float factor = 7.0f / 8;
          dstp[j] = (pixel_t)(dstp[j] * factor);
        }
      }
      else {
        if constexpr (sizeof(pixel_t) != 4) {
          constexpr int range_min = 16 << (bits_per_pixel - 8);
          constexpr int n = range_min - ((range_min * 7) >> 3);
          dstp[j] = (pixel_t)(((dstp[j] * 7) >> 3) + n); // (_dstp[j] - range_min) * 7) >> 3) + range_min);
        }
        else {
          constexpr float range_min_f = 16.0f / 255.0f;
          dstp[j] = (pixel_t)(((dstp[j] - range_min_f) * 7 / 8) + range_min_f);
        }
      }
    }
  }
}

template<typename pixel_t, int bits_per_pixel, bool fadeBackground, bool isRGB>
void LightOneUVPixel(const bool lightIt, pixel_t* dstpU, int j, pixel_t& color_u, pixel_t* dstpV, pixel_t& color_v)
{
  if (lightIt) { // lit if any font pixels are on under the 1-2-4 pixel-wide mask
      dstpU[j] = color_u; // originally: neutral U and V (128)
      dstpV[j] = color_v;
  }
  else {
    // speed optimization: one subtraction less
    // (((U - 128) * 7) >> 3) + 128 = ((U * 7) >> 3) + 16
    // in general: ((U * 7) >> 3) + n where n = range_half - ((range_half * 7) >> 3)
    if constexpr (fadeBackground) {
      if constexpr (sizeof(pixel_t) != 4) {
        constexpr int range_half = 1 << (bits_per_pixel - 1);
        constexpr int n = range_half - ((range_half * 7) >> 3);
        dstpU[j] = (pixel_t)(((dstpU[j] * 7) >> 3) + n); // ((((U - range_half) * 7) >> 3) + range_half);
        dstpV[j] = (pixel_t)(((dstpV[j] * 7) >> 3) + n);
      }
      else {
#ifdef FLOAT_CHROMA_IS_HALF_CENTERED
        constexpr float shift = 0.5f;
#else
        constexpr float shift = 0.0f;
#endif
        constexpr float factor = 7.0f / 8.0f;
        dstpU[j] = (pixel_t)(((dstpU[j] - shift) * factor) + shift);
        dstpV[j] = (pixel_t)(((dstpV[j] - shift) * factor) + shift);
      }
    }
  }
}

static void adjustWriteLimits(std::vector<int>& s, const int width, const int height, const int FONT_WIDTH, const int FONT_HEIGHT, int align, int& x, int& y, int& len, int& startindex, int& xstart, int& ystart, int& yend)
{
  const int al = alignToBitmask(align);

  // alignment X
  if (al & ATA_RIGHT)
    x -= (FONT_WIDTH * len + 1);
  else if (al & ATA_CENTER)
    x -= (FONT_WIDTH * len / 2);

  // alignment Y
  if (al & ATA_BASELINE)
    y -= FONT_HEIGHT / 2;
  else if (al & ATA_BOTTOM)
    y -= (FONT_HEIGHT + 1);

  // Chop text if exceed right margin
  if (len * FONT_WIDTH > width - x)
    len = (width - x) / FONT_WIDTH;

  startindex = 0;
  xstart = 0;
  // Chop 1st char if exceed left margin
  if (x < 0) {
    startindex = (-x) / FONT_WIDTH;
    xstart = (-x) % FONT_WIDTH;
    x = 0;
  }

  ystart = 0;
  yend = FONT_HEIGHT;
  // Chop font if exceed bottom margin
  if (y > height - FONT_HEIGHT)
    yend = height - y;

  // Chop font if exceed top margin
  if (y < 0) {
    ystart = -y;
    y = 0;
  }

  // Roll in start index
  if (startindex > 0) {
    s.erase(s.begin(), s.begin() + startindex - 1);
    len -= startindex;
  }
}

template<int bits_per_pixel, bool fadeBackground, bool isRGB>
void do_DrawStringPlanar(const int width, const int height, BYTE **dstps, int *pitches, const int logXRatioUV, const int logYRatioUV, const int planeCount,
  const BitmapFont *bmfont, int x, int y, std::vector<int>& s, int color, int halocolor, int align, bool useHalocolor)
{

  // define pixel_t as uint8_t, uint16_t or float, based on bits_per_pixel
  typedef typename std::conditional<bits_per_pixel == 8, uint8_t, typename std::conditional < bits_per_pixel <= 16, uint16_t, float > ::type >::type pixel_t;

  std::vector<uint16_t> current_outlined_char(bmfont->height);
  const uint16_t* fonts = bmfont->font_bitmaps.data();
  const int FONT_WIDTH = bmfont->width;
  const int FONT_HEIGHT = bmfont->height;

  // x, y: pixels
  int planes_y[4] = { PLANAR_Y, PLANAR_U, PLANAR_V, PLANAR_A };
  int planes_r[4] = { PLANAR_G, PLANAR_B, PLANAR_R, PLANAR_A };
  int* planes = isRGB ? planes_r : planes_y;

  const int pixelsize = sizeof(pixel_t);

  // Default string length
  int len = (int)s.size();
  int startindex;
  int xstart;
  int ystart;
  int yend;

  adjustWriteLimits(s, width, height, FONT_WIDTH, FONT_HEIGHT, align,
    // adjusted parameters
    x, y, len, startindex, xstart, ystart, yend);

  if (len <= 0)
    return;

  pixel_t val_color;
  pixel_t val_color_outline;

  // some helper lambdas
  auto getHBDColor_UV = [](int color) {
    if constexpr (bits_per_pixel < 32)
      return (pixel_t)(color << (bits_per_pixel - 8));
#ifdef FLOAT_CHROMA_IS_HALF_CENTERED
    constexpr float shift = 0.5f;
#else
    constexpr float shift = 0.0f;
#endif
    return (pixel_t)((color - 128) / 255.0f + shift); // 32 bit float chroma 128=0.5
    // FIXME: consistently using limited->fullscale conversion for float
  };

  auto getHBDColor_Y = [](int color) {
    if constexpr (bits_per_pixel < 32)
      return (pixel_t)(color << (bits_per_pixel - 8));
    return (pixel_t)(color / 255.0f); // 0..255 -> 0..1.0
    // FIXME: consistently using limited->fullscale conversion for float
  };

  auto getHBDColor_RGB = [](int color) {
    if constexpr (bits_per_pixel <= 16) {
      constexpr int max_pixel_value = (1 << (bits_per_pixel & 31)) - 1;
      return (pixel_t)((float)color * max_pixel_value / 255); // 0..255 --> 0..1023,4095,16383,65535
    }
    return (pixel_t)(color / 255.0f); // 0..255 -> 0..1.0
  };

  for (int p = 0; p < planeCount; p++)
  {
    int plane = planes[p];

    if (!isRGB && plane != PLANAR_Y)
      continue; // handle U and V specially. Y, R, G, B is O.K.

    int planecolor = getColorForPlane(plane, color);
    int planecolor_outline = getColorForPlane(plane, halocolor);
    if (isRGB) {
      val_color = getHBDColor_RGB(planecolor);
      val_color_outline = getHBDColor_RGB(planecolor_outline);
    }
    else if (plane == PLANAR_U || plane == PLANAR_V) {
      val_color = getHBDColor_UV(planecolor);
      val_color_outline = getHBDColor_UV(planecolor_outline);
    }
    else {// Y
      val_color = getHBDColor_Y(planecolor);
      val_color_outline = getHBDColor_Y(planecolor_outline);
    }

    const int pitch = pitches[p];
    BYTE* dstp = dstps[p] + x * pixelsize + y * pitch;

    // Start rendering
    for (int ty = ystart; ty < yend; ty++) {
      int num = s[0];

      unsigned int fontline;
      unsigned int fontoutline;

      fontline = fonts[num * FONT_HEIGHT + ty] << xstart; // shift some pixels if leftmost is chopped

      if (useHalocolor) {
        bmfont->generateOutline(current_outlined_char.data(), num); // on the fly, can be
        fontoutline = current_outlined_char[ty] << xstart; // shift some pixels if leftmost is chopped
      }

      int current_xstart = xstart; // leftmost can be chopped
      int j = 0;
      pixel_t* _dstp = reinterpret_cast<pixel_t*>(dstp);

      for (int i = 0; i < len; i++) {
        for (int tx = current_xstart; tx < FONT_WIDTH; tx++) {
          const bool lightIt = fontline & 0x8000;
          LightOnePixel<pixel_t, bits_per_pixel, fadeBackground, isRGB>(lightIt, _dstp, j, val_color);
          if (useHalocolor) {
            if (!lightIt) // it can be outline
              LightOnePixel<pixel_t, bits_per_pixel, fadeBackground, isRGB>(fontoutline & 0x8000, _dstp, j, val_color_outline);
          }
          j += 1;
          fontline <<= 1; // next pixel to the left
          if (useHalocolor)
            fontoutline <<= 1;
        }
        current_xstart = 0; // further characters are not chopped

        if (i + 1 < len)
        {
          num = s[i + 1];
          if (useHalocolor) {
            bmfont->generateOutline(current_outlined_char.data(), num);
            fontoutline = current_outlined_char[ty]; // shift some pixels if leftmost is chopped
          }
          fontline = fonts[num * FONT_HEIGHT + ty];
        }
      }
      dstp += pitch;
    }
  }

  if constexpr (isRGB)
    return;

  if (planeCount < 3)
    return; // Y

  // draw U and V in one step
  pixel_t color_u = getHBDColor_UV((color >> 8) & 0xff);
  pixel_t color_v = getHBDColor_UV(color & 0xff);
  pixel_t color_outline_u = getHBDColor_UV((halocolor >> 8) & 0xff);
  pixel_t color_outline_v = getHBDColor_UV(halocolor & 0xff);

  const int pitchUV = pitches[1];

  // .SubS = 1, 2 or 4
  const int xSubS = 1 << logXRatioUV;
  const int ySubS = 1 << logYRatioUV;
  const int offset = (x >> logXRatioUV) * pixelsize + (y >> logYRatioUV) * pitchUV;

  BYTE* dstpU = dstps[1] + offset;
  BYTE* dstpV = dstps[2] + offset;

  // fontmask = 0x2000000, 0x3000000 or 0x3C00000; 1, 2 or 4 bits
  unsigned int fontmask = 0;
  for (int i = 0; i < xSubS; i++) {
    fontmask >>= 1;
    fontmask |= 0x8000 << FONT_WIDTH;
  }

  /*
        U and V handling, multiple possible source for a given
        01 23 45 67 89 01
        .. .. OO O. .. ..
        .. .O .. .O .. ..
        .. .O .. .O .. ..
  */

  for (int ty = ystart; ty < yend; ty += ySubS) {
    int i, j, num;
    unsigned int fontline = 0;
    unsigned int fontoutline = 0;

    // two characters at a time

    num = s[0];
    // Or in vertical subsampling of glyph
    for (int m = 0; m < ySubS; m++) fontline |= fonts[num * FONT_HEIGHT + ty + m];
    if (useHalocolor) {
      bmfont->generateOutline(current_outlined_char.data(), num); // on the fly, can be
      for (int m = 0; m < ySubS; m++) fontoutline |= current_outlined_char[ty + m];
    }
    //             AAAAAAAAAA000000
    //             BBBBBBBBBB000000
    //             aaaaaaaaaa000000 // or'd

    fontline <<= FONT_WIDTH; // Move 1st glyph up
    if (useHalocolor) fontoutline <<= FONT_WIDTH;
    //   aaaaaaaaaa0000000000000000 // shift up by 10

    if (1 < len) {
      num = s[1];
      // Or in vertical subsampling of 2nd glyph
      for (int m = 0; m < ySubS; m++) fontline |= fonts[num * FONT_HEIGHT + ty + m];
      if (useHalocolor) {
        bmfont->generateOutline(current_outlined_char.data(), num); // on the fly, can be
        for (int m = 0; m < ySubS; m++) fontoutline |= current_outlined_char[ty + m];
      }
    }

    //   aaaaaaaaaa0000000000000000
    //             CCCCCCCCCC000000
    //             DDDDDDDDDD000000
    //   aaaaaaaaaabbbbbbbbbb000000 // two fonts together
    //   fontmasks  (depends on horizontal subsampling)
    //   1000......................
    //   1100......................
    //   1111......................

    // Cope with left crop of glyph
    fontline <<= xstart;
    if (useHalocolor) fontoutline <<= xstart;
    int _xs = xstart;

    pixel_t* _dstpU = reinterpret_cast<pixel_t*>(dstpU);
    pixel_t* _dstpV = reinterpret_cast<pixel_t*>(dstpV);

    // handle two characters at a time because one pixel may consist of two fonts
    // when we have horizontal subsampling.
    // Note: extremely ugly for 411!
    for (i = 1, j = 0; i < len; i += 2) {
      for (int tx = _xs; tx < 2 * FONT_WIDTH; tx += xSubS) {
        const bool lightIt = (fontline & fontmask);
        LightOneUVPixel<pixel_t, bits_per_pixel, fadeBackground, isRGB>(lightIt, _dstpU, j, color_u, _dstpV, color_v);
        if (useHalocolor) {
          if (!lightIt) // it can be outline
            LightOneUVPixel<pixel_t, bits_per_pixel, fadeBackground, isRGB>(fontoutline & fontmask, _dstpU, j, color_outline_u, _dstpV, color_outline_v);
        }
        j += 1;
        fontline <<= xSubS;
        if (useHalocolor)
          fontoutline <<= xSubS;
      }
      _xs = 0;
      fontline = 0;
      if (useHalocolor)
        fontoutline = 0;

      if (i + 1 < len) {
        num = s[i + 1];
        for (int m = 0; m < ySubS; m++) fontline |= fonts[num * FONT_HEIGHT + ty + m];;
        fontline <<= FONT_WIDTH;

        if (useHalocolor) {
          bmfont->generateOutline(current_outlined_char.data(), num); // on the fly, can be
          for (int m = 0; m < ySubS; m++) fontoutline |= current_outlined_char[ty + m];
          fontoutline <<= FONT_WIDTH;
        }

        if (i + 2 < len) {
          num = s[i + 2];
          for (int m = 0; m < ySubS; m++) fontline |= fonts[num * FONT_HEIGHT + ty + m];

          if (useHalocolor) {
            bmfont->generateOutline(current_outlined_char.data(), num); // on the fly, can be
            for (int m = 0; m < ySubS; m++) fontoutline |= current_outlined_char[ty + m];
          }
        }
      }
    }

    // Do odd length last glyph
    if (i == len) {
      for (int tx = _xs; tx < FONT_WIDTH; tx += xSubS) {
        const bool lightIt = (fontline & fontmask);
        LightOneUVPixel<pixel_t, bits_per_pixel, fadeBackground, isRGB>(lightIt, _dstpU, j, color_u, _dstpV, color_v);
        if (useHalocolor) {
          if (!lightIt) // it can be outline
            LightOneUVPixel<pixel_t, bits_per_pixel, fadeBackground, isRGB>(fontoutline & fontmask, _dstpU, j, color_outline_u, _dstpV, color_outline_v);
        }
        j += 1;
        fontline <<= xSubS;
        if (useHalocolor)
          fontoutline <<= xSubS;
      }
    }
    dstpU += pitchUV;
    dstpV += pitchUV;
  }
}

template<bool fadeBackground>
static void do_DrawStringYUY2(const int width, const int height, BYTE* _dstp, int pitch, const BitmapFont *bmfont, int x, int y, std::vector<int>& s, int color, int halocolor, int align, bool useHalocolor)
{
  // fixedFontRec_t current_outlined_char;

  std::vector<uint16_t> current_outlined_char(bmfont->height);
  const uint16_t *fonts = bmfont->font_bitmaps.data();
  const int FONT_WIDTH = bmfont->width;
  const int FONT_HEIGHT = bmfont->height;

  // Default string length
  int len = (int)s.size();
  int startindex;
  int xstart;
  int ystart;
  int yend;

  adjustWriteLimits(s, width, height, FONT_WIDTH, FONT_HEIGHT, align,
    // adjusted parameters
    x, y, len, startindex, xstart, ystart, yend);

  if (len <= 0)
    return;

  int val_color = getColorForPlane(PLANAR_Y, color);
  int val_color_outline = getColorForPlane(PLANAR_Y, halocolor);
  int val_color_U = getColorForPlane(PLANAR_U, color);
  int val_color_U_outline = getColorForPlane(PLANAR_U, halocolor);
  int val_color_V = getColorForPlane(PLANAR_V, color);
  int val_color_V_outline = getColorForPlane(PLANAR_V, halocolor);

  BYTE* dstp = _dstp + x * 2 + y * pitch;

  // Start rendering
  for (int ty = ystart; ty < yend; ty++, dstp += pitch) {
    BYTE* dp = dstp;

    int num = s[0];

    unsigned int fontline;
    unsigned int fontoutline;

    fontline = fonts[num * FONT_HEIGHT + ty] << xstart; // shift some pixels if leftmost is chopped

    if (useHalocolor) {
      bmfont->generateOutline(current_outlined_char.data(), num); // on the fly, can be
      fontoutline = current_outlined_char[ty] << xstart; // shift some pixels if leftmost is chopped
    }

    int current_xstart = xstart; // leftmost can be chopped

    for (int i = 0; i < len; i++) {
      for (int tx = current_xstart; tx < FONT_WIDTH; tx++) {
        const bool lightIt = fontline & 0x8000;
        LightOnePixelYUY2<fadeBackground>(lightIt, dp, val_color, val_color_U, val_color_V);
        if (useHalocolor) {
          if (!lightIt) // it can be outline
            LightOnePixelYUY2<fadeBackground>(fontoutline & 0x8000, dp, val_color_outline, val_color_U_outline, val_color_V_outline);
        }
        dp += 2;
        fontline <<= 1; // next pixel to the left
        if (useHalocolor)
          fontoutline <<= 1;
      }

      current_xstart = 0;

      if (i + 1 < len)
      {
        num = s[i + 1];
        if (useHalocolor) {
          bmfont->generateOutline(current_outlined_char.data(), num);
          fontoutline = current_outlined_char[ty]; // shift some pixels if leftmost is chopped
        }
        fontline = fonts[num * FONT_HEIGHT + ty];
      }
    }
  }
}

template<int bits_per_pixel, int rgbstep, bool fadeBackground>
static void do_DrawStringPackedRGB(const int width, const int height, BYTE* _dstp, int pitch, const BitmapFont *bmfont, int x, int y, std::vector<int>& s, int color, int halocolor, int align, bool useHalocolor)
{
  // define pixel_t as uint8_t, uint16_t or float, based on bits_per_pixel
  typedef typename std::conditional<bits_per_pixel == 8, uint8_t, typename std::conditional < bits_per_pixel <= 16, uint16_t, float > ::type >::type pixel_t;

  auto getHBDColor_RGB = [](int color) {
    if constexpr (bits_per_pixel <= 16) {
      constexpr int max_pixel_value = (1 << bits_per_pixel) - 1;
      return (pixel_t)(color * max_pixel_value / 255); // 0..255 --> 0..1023,4095,16383,65535
    }
    return (pixel_t)(color / 255.0f); // 0..255 -> 0..1.0
  };


  std::vector<uint16_t> current_outlined_char(bmfont->height);
  const uint16_t* fonts = bmfont->font_bitmaps.data();
  const int FONT_WIDTH = bmfont->width;
  const int FONT_HEIGHT = bmfont->height;


  // Default string length
  int len = (int)s.size();
  int startindex;
  int xstart;
  int ystart;
  int yend;

  adjustWriteLimits(s, width, height, FONT_WIDTH, FONT_HEIGHT, align,
    // adjusted parameters
    x, y, len, startindex, xstart, ystart, yend);

  if (len <= 0)
    return;

  int val_color_R = getHBDColor_RGB(getColorForPlane(PLANAR_R, color));
  int val_color_R_outline = getHBDColor_RGB(getColorForPlane(PLANAR_R, halocolor));
  int val_color_G = getHBDColor_RGB(getColorForPlane(PLANAR_G, color));
  int val_color_G_outline = getHBDColor_RGB(getColorForPlane(PLANAR_G, halocolor));
  int val_color_B = getHBDColor_RGB(getColorForPlane(PLANAR_B, color));
  int val_color_B_outline = getHBDColor_RGB(getColorForPlane(PLANAR_B, halocolor));

  // upside down
  BYTE* dstp = _dstp + x * rgbstep + (height - 1 - y) * pitch;;

  // Start rendering
  for (int ty = ystart; ty < yend; ty++, dstp -= pitch) {
    BYTE* dp = dstp;

    int num = s[0];

    unsigned int fontline;
    unsigned int fontoutline;

    fontline = fonts[num * FONT_HEIGHT + ty] << xstart; // shift some pixels if leftmost is chopped

    if (useHalocolor) {
      bmfont->generateOutline(current_outlined_char.data(), num); // on the fly, can be
      fontoutline = current_outlined_char[ty] << xstart; // shift some pixels if leftmost is chopped
    }

    int current_xstart = xstart; // leftmost can be chopped

    for (int i = 0; i < len; i++) {
      for (int tx = current_xstart; tx < FONT_WIDTH; tx++) {
        const bool lightIt = fontline & 0x8000;
        LightOnePixelRGB<pixel_t, fadeBackground>(lightIt, dp, val_color_R, val_color_G, val_color_B);
        if (useHalocolor) {
          if (!lightIt) // it can be outline
            LightOnePixelRGB<pixel_t, fadeBackground>(fontoutline & 0x8000, dp, val_color_R_outline, val_color_G_outline, val_color_B_outline);
        }
        dp += rgbstep;
        fontline <<= 1; // next pixel to the left
        if (useHalocolor)
          fontoutline <<= 1;
      }

      current_xstart = 0;

      if (i + 1 < len)
      {
        num = s[i + 1];
        if (useHalocolor) {
          bmfont->generateOutline(current_outlined_char.data(), num);
          fontoutline = current_outlined_char[ty]; // shift some pixels if leftmost is chopped
        }
        fontline = fonts[num * FONT_HEIGHT + ty];
      }
    }
  }
}

static bool strequals_i(const std::string& a, const std::string& b)
{
  return std::equal(a.begin(), a.end(),
    b.begin(), b.end(),
    [](char a, char b) {
      return tolower(a) == tolower(b);
    });
}

// in fixedfonts.cpp
extern const uint16_t *font_bitmaps[];
extern const uint16_t *font_codepoints[];
extern const FixedFont_info_t *font_infos[];

std::unique_ptr<BitmapFont> GetBitmapFont(int size, const char *name, bool bold, bool debugSave) {

  BitmapFont* current_font = nullptr;

  // check internal embedded fonts
  bool found = false;

  // find font in internal list
  for (int i = 0; i < PREDEFINED_FONT_COUNT; i++)
  {
    const FixedFont_info_t* fi = font_infos[i];
    if (fi->height == size && fi->bold == bold && strequals_i(fi->fontname, name)) {
      current_font = new BitmapFont(
        fi->charcount,
        font_bitmaps[i],
        font_codepoints[i],
        fi->width,
        fi->height,
        fi->fontname,
        "",
        fi->bold,
        false);
      found = true;
      break;
    }
  }
  // pass #2 when size does not match exactly, find nearest, but still smaller font.
  if (!found) {
    // find font i internal list
    int last_good_size = 0;
    int found_index = -1;
    for (int i = 0; i < PREDEFINED_FONT_COUNT; i++)
    {
      const FixedFont_info_t* fi = font_infos[i];
      if (fi->bold == bold && strequals_i(fi->fontname, name)) {
        if (last_good_size == 0) {
          found_index = i;
          last_good_size = fi->height;
        }
        else if (std::abs(fi->height - size) < std::abs(last_good_size - size) && fi->height <= size) {
          // has better size match and is not larger
          found_index = i;
          last_good_size = fi->height;
        }
      }
    }
    if (found_index >= 0) {
      const FixedFont_info_t* fi = font_infos[found_index];
      current_font = new BitmapFont(
        fi->charcount,
        font_bitmaps[found_index],
        font_codepoints[found_index],
        fi->width,
        fi->height,
        fi->fontname,
        "",
        fi->bold,
        false);
      found = true;
    }
  }

  if (!found) {
    // fixme: make cache
    BdfFont bdf;
    bdf = LoadBMF(name, bold);
    if (bdf.font_info.chars == 0)
      return nullptr;

    current_font = new BitmapFont(
      bdf.font_info.chars,
      bdf.font_bitmaps.data(),
      bdf.codepoints_array.data(),
      bdf.font_info.font_bounding_box_x,
      bdf.font_info.font_bounding_box_y,
      bdf.font_info.font,
      bdf.font_filename,
      strequals_i(bdf.font_properties.Weight_name, "bold"),
      debugSave);
  }
  return std::unique_ptr<BitmapFont>(current_font);
}

static void DrawString_internal(BitmapFont *current_font, const VideoInfo& vi, PVideoFrame& dst, int x, int y, std::wstring &s16, int color, int halocolor, bool useHalocolor, int align, bool fadeBackground)
{
  //static BitmapFont_10_20 infoFont1020; // constructor runs once, single instance

  // map unicode to character map index
  auto s_remapped = current_font->remap(s16); // array of font table indexes

  //SaveBitmapSource(); // debug to generate source from original table

  const bool isRGB = vi.IsRGB();
  const int planes_y[4] = { PLANAR_Y, PLANAR_U, PLANAR_V, PLANAR_A };
  const int planes_r[4] = { PLANAR_G, PLANAR_B, PLANAR_R, PLANAR_A };
  const int* planes = isRGB ? planes_r : planes_y;

  int logXRatioUV = 0;
  int logYRatioUV = 0;
  if (!vi.IsY() && !vi.IsRGB()) {
    logXRatioUV = vi.IsYUY2() ? 1 : vi.GetPlaneWidthSubsampling(PLANAR_U);
    logYRatioUV = vi.IsYUY2() ? 0 : vi.GetPlaneHeightSubsampling(PLANAR_U);
  }
  int planecount = vi.IsYUY2() ? 1 : std::min(vi.NumComponents(), 3);
  BYTE* dstps[3];
  int pitches[3];

  for (int i = 0; i < planecount; i++)
  {
    int plane = planes[i];
    dstps[i] = dst->GetWritePtr(plane);
    pitches[i] = dst->GetPitch(plane);
  }

  const int width = vi.width;
  const int height = vi.height;

  // fixme: put parameter to a single struct

  const int bits_per_pixel = vi.BitsPerComponent();

  if (vi.IsYUY2()) {
    if (fadeBackground)
      do_DrawStringYUY2<true>(width, height, dstps[0], pitches[0], current_font, x, y, s_remapped, color, halocolor, align, useHalocolor);
    else
      do_DrawStringYUY2<false>(width, height, dstps[0], pitches[0], current_font, x, y, s_remapped, color, halocolor, align, useHalocolor);
    return;
  }

  // Packed RGB24/32/48/64
  if (isRGB && !vi.IsPlanar()) {
    if (fadeBackground) {
      if (vi.IsRGB24())
        do_DrawStringPackedRGB<8, 3, true>(width, height, dstps[0], pitches[0], current_font, x, y, s_remapped, color, halocolor, align, useHalocolor);
      else if (vi.IsRGB32())
        do_DrawStringPackedRGB<8, 4, true>(width, height, dstps[0], pitches[0], current_font, x, y, s_remapped, color, halocolor, align, useHalocolor);
      else if (vi.IsRGB48())
        do_DrawStringPackedRGB<16, 6, true>(width, height, dstps[0], pitches[0], current_font, x, y, s_remapped, color, halocolor, align, useHalocolor);
      else if (vi.IsRGB64())
        do_DrawStringPackedRGB<16, 8, true>(width, height, dstps[0], pitches[0], current_font, x, y, s_remapped, color, halocolor, align, useHalocolor);
    }
    else {
      if (vi.IsRGB24())
        do_DrawStringPackedRGB<8, 3, false>(width, height, dstps[0], pitches[0], current_font, x, y, s_remapped, color, halocolor, align, useHalocolor);
      else if (vi.IsRGB32())
        do_DrawStringPackedRGB<8, 4, false>(width, height, dstps[0], pitches[0], current_font, x, y, s_remapped, color, halocolor, align, useHalocolor);
      else if (vi.IsRGB48())
        do_DrawStringPackedRGB<16, 6, false>(width, height, dstps[0], pitches[0], current_font, x, y, s_remapped, color, halocolor, align, useHalocolor);
      else if (vi.IsRGB64())
        do_DrawStringPackedRGB<16, 8, false>(width, height, dstps[0], pitches[0], current_font, x, y, s_remapped, color, halocolor, align, useHalocolor);
    }
    return;
  }

  // planar and Y
  if (fadeBackground) {
    if (isRGB) {
      switch (bits_per_pixel)
      {
        // FIXME: we have outline inside there, pass halocolor and an option: bool outline
      case 8: do_DrawStringPlanar<8, true, true>(width, height, dstps, pitches, logXRatioUV, logYRatioUV, planecount, current_font, x, y, s_remapped, color, halocolor, align, useHalocolor); break;
      case 10: do_DrawStringPlanar<10, true, true>(width, height, dstps, pitches, logXRatioUV, logYRatioUV, planecount, current_font, x, y, s_remapped, color, halocolor, align, useHalocolor); break;
      case 12: do_DrawStringPlanar<12, true, true>(width, height, dstps, pitches, logXRatioUV, logYRatioUV, planecount, current_font, x, y, s_remapped, color, halocolor, align, useHalocolor); break;
      case 14: do_DrawStringPlanar<14, true, true>(width, height, dstps, pitches, logXRatioUV, logYRatioUV, planecount, current_font, x, y, s_remapped, color, halocolor, align, useHalocolor); break;
      case 16: do_DrawStringPlanar<16, true, true>(width, height, dstps, pitches, logXRatioUV, logYRatioUV, planecount, current_font, x, y, s_remapped, color, halocolor, align, useHalocolor); break;
      case 32: do_DrawStringPlanar<32, true, true>(width, height, dstps, pitches, logXRatioUV, logYRatioUV, planecount, current_font, x, y, s_remapped, color, halocolor, align, useHalocolor); break;
      }
    }
    else {
      switch (bits_per_pixel)
      {
      case 8: do_DrawStringPlanar<8, true, false>(width, height, dstps, pitches, logXRatioUV, logYRatioUV, planecount, current_font, x, y, s_remapped, color, halocolor, align, useHalocolor); break;
      case 10: do_DrawStringPlanar<10, true, false>(width, height, dstps, pitches, logXRatioUV, logYRatioUV, planecount, current_font, x, y, s_remapped, color, halocolor, align, useHalocolor); break;
      case 12: do_DrawStringPlanar<12, true, false>(width, height, dstps, pitches, logXRatioUV, logYRatioUV, planecount, current_font, x, y, s_remapped, color, halocolor, align, useHalocolor); break;
      case 14: do_DrawStringPlanar<14, true, false>(width, height, dstps, pitches, logXRatioUV, logYRatioUV, planecount, current_font, x, y, s_remapped, color, halocolor, align, useHalocolor); break;
      case 16: do_DrawStringPlanar<16, true, false>(width, height, dstps, pitches, logXRatioUV, logYRatioUV, planecount, current_font, x, y, s_remapped, color, halocolor, align, useHalocolor); break;
      case 32: do_DrawStringPlanar<32, true, false>(width, height, dstps, pitches, logXRatioUV, logYRatioUV, planecount, current_font, x, y, s_remapped, color, halocolor, align, useHalocolor); break;
      }
    }
  }
  else {
    if (isRGB) {
      switch (bits_per_pixel)
      {
      case 8: do_DrawStringPlanar<8, false, true>(width, height, dstps, pitches, logXRatioUV, logYRatioUV, planecount, current_font, x, y, s_remapped, color, halocolor, align, useHalocolor); break;
      case 10: do_DrawStringPlanar<10, false, true>(width, height, dstps, pitches, logXRatioUV, logYRatioUV, planecount, current_font, x, y, s_remapped, color, halocolor, align, useHalocolor); break;
      case 12: do_DrawStringPlanar<12, false, true>(width, height, dstps, pitches, logXRatioUV, logYRatioUV, planecount, current_font, x, y, s_remapped, color, halocolor, align, useHalocolor); break;
      case 14: do_DrawStringPlanar<14, false, true>(width, height, dstps, pitches, logXRatioUV, logYRatioUV, planecount, current_font, x, y, s_remapped, color, halocolor, align, useHalocolor); break;
      case 16: do_DrawStringPlanar<16, false, true>(width, height, dstps, pitches, logXRatioUV, logYRatioUV, planecount, current_font, x, y, s_remapped, color, halocolor, align, useHalocolor); break;
      case 32: do_DrawStringPlanar<32, false, true>(width, height, dstps, pitches, logXRatioUV, logYRatioUV, planecount, current_font, x, y, s_remapped, color, halocolor, align, useHalocolor); break;
      }
    }
    else {
      switch (bits_per_pixel)
      {
      case 8: do_DrawStringPlanar<8, false, false>(width, height, dstps, pitches, logXRatioUV, logYRatioUV, planecount, current_font, x, y, s_remapped, color, halocolor, align, useHalocolor); break;
      case 10: do_DrawStringPlanar<10, false, false>(width, height, dstps, pitches, logXRatioUV, logYRatioUV, planecount, current_font, x, y, s_remapped, color, halocolor, align, useHalocolor); break;
      case 12: do_DrawStringPlanar<12, false, false>(width, height, dstps, pitches, logXRatioUV, logYRatioUV, planecount, current_font, x, y, s_remapped, color, halocolor, align, useHalocolor); break;
      case 14: do_DrawStringPlanar<14, false, false>(width, height, dstps, pitches, logXRatioUV, logYRatioUV, planecount, current_font, x, y, s_remapped, color, halocolor, align, useHalocolor); break;
      case 16: do_DrawStringPlanar<16, false, false>(width, height, dstps, pitches, logXRatioUV, logYRatioUV, planecount, current_font, x, y, s_remapped, color, halocolor, align, useHalocolor); break;
      case 32: do_DrawStringPlanar<32, false, false>(width, height, dstps, pitches, logXRatioUV, logYRatioUV, planecount, current_font, x, y, s_remapped, color, halocolor, align, useHalocolor); break;
      }
    }
  }
}

void SimpleTextOutW(BitmapFont *current_font, const VideoInfo& vi, PVideoFrame& frame, int real_x, int real_y, std::wstring& text, bool fadeBackground, int textcolor, int halocolor, bool useHaloColor, int align)
{
  DrawString_internal(current_font, vi, frame, real_x, real_y, text, textcolor, halocolor, useHaloColor, align, fadeBackground); // fully transparent background
}

// additional parameter: lsp line spacing
void SimpleTextOutW_multi(BitmapFont *current_font, const VideoInfo& vi, PVideoFrame& frame, int real_x, int real_y, std::wstring& text, bool fadeBackground, int textcolor, int halocolor, bool useHaloColor, int align, int lsp)
{

  // make list governed by LF separator
  using wstringstream = std::basic_stringstream<wchar_t>;
  std::wstring temp;
  std::vector<std::wstring> parts;
  wstringstream wss(text);
  while (std::getline(wss, temp, L'\n'))
    parts.push_back(temp);

  const int fontSize = current_font->height;

  // when multiline, bottom and vertically centered cases affect starting y
  int al = alignToBitmask(align);
  if (al & ATA_BOTTOM)
    real_y -= fontSize * ((int)parts.size() - 1);
  else if (al & ATA_BASELINE)
    real_y -= fontSize * ((int)parts.size() / 2);

  for (auto ws : parts) {
    SimpleTextOutW(current_font, vi, frame, real_x, real_y, ws, fadeBackground, textcolor, halocolor, useHaloColor, align);
    real_y += fontSize + lsp;
  }
}

// Old legacy info.h functions, but with utf8 mode
// w/o outline, originally with ASCII input, background fading
// unline name Planar, it works for all format
void DrawStringPlanar(VideoInfo& vi, PVideoFrame& dst, int x, int y, const char* s)
{
  int color;
  if (vi.IsRGB())
    color = (250 << 16) + (250 << 8) + (250);
  else
    color = (230 << 16) + (128 << 8) + (128);

  // fadeBackground = true: background letter area is faded instead not being untouched.

  std::wstring ws = charToWstring(s, false);

  int halocolor = 0;

  std::unique_ptr<BitmapFont> current_font = GetBitmapFont(20, "info_h", false, false); // 10x20

  if (current_font == nullptr)
    return;

  DrawString_internal(current_font.get(), vi, dst, x, y, ws,
    color,
    halocolor,
    false, // don't use halocolor
    0 /* no align */,
    true // fadeBackGround
  );

}

void DrawStringYUY2(VideoInfo& vi, PVideoFrame& dst, int x, int y, const char* s)
{
  DrawStringPlanar(vi, dst, x, y, s); // same
}

// legacy function w/o outline, originally with ASCII input, background fading
void DrawStringRGB24(VideoInfo &vi, PVideoFrame& dst, int x, int y, const char* s)
{
  DrawStringPlanar(vi, dst, x, y, s); // same
}

// legacy function w/o outline, originally with ASCII input, background fading
void DrawStringRGB32(VideoInfo& vi, PVideoFrame& dst, int x, int y, const char* s)
{
  DrawStringPlanar(vi, dst, x, y, s); // same
}
